package com.stars.easyms.datasource;

import com.github.pagehelper.Dialect;
import com.github.pagehelper.util.ExecutorUtil;
import com.stars.easyms.datasource.bean.PageCountDelayHandleBean;
import com.stars.easyms.datasource.holder.EasyMsInterceptorSkipHolder;
import com.stars.easyms.datasource.page.PageParameter;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.exceptions.ExceptionFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.*;
import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.transaction.TransactionFactory;
import org.springframework.beans.BeanUtils;

import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

/**
 * <p>className: EasyMsQueryExecutor</p>
 * <p>description: 自定义查询分页时的总数量工具类</p>
 *
 * @author guoguifang
 * @version 1.2.2
 * @date 2019-08-07 20:05
 */
@Slf4j
public final class EasyMsQueryExecutor {

    private EasyMsDataSourceMonitor easyMsDataSourceMonitor = EasyMsDataSourceMonitor.getInstance();

    /**
     * 执行手动设置的 count 查询，该查询支持的参数必须和被分页的方法相同
     */
    public Long executeManualCount(Configuration configuration, MappedStatement countMs, Object parameter, BoundSql boundSql,
                                   ResultHandler resultHandler) throws SQLException {
        Executor executor = null;
        try {
            executor = newExecutor(configuration);
            CacheKey countKey = executor.createCacheKey(countMs, parameter, RowBounds.DEFAULT, boundSql);
            BoundSql countBoundSql = countMs.getBoundSql(parameter);

            // 开启监控
            easyMsDataSourceMonitor.start(countMs.getId(), countBoundSql);

            // 执行count查询无需拦截
            EasyMsInterceptorSkipHolder.skip();

            // 执行查询count
            Object countResultList = executor.query(countMs, parameter, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql);
            return ((Number) ((List) countResultList).get(0)).longValue();
        } catch (SQLException e) {
            // 异常情况监控
            easyMsDataSourceMonitor.sqlException(e);
            throw e;
        } finally {
            // 清除EasyMsInterceptorSkipHolder
            EasyMsInterceptorSkipHolder.clear();

            // 结束监控
            easyMsDataSourceMonitor.end();

            // 提交并关闭executor
            if (executor != null) {
                commitAndClose(executor);
            }
        }
    }

    /**
     * 执行自动生成的 count 查询
     */
    public Long executeAutoCount(Dialect dialect, Configuration configuration, MappedStatement countMs, Object parameter, BoundSql boundSql,
                                 RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        Executor executor = null;
        try {
            executor = newExecutor(configuration);
            Map<String, Object> additionalParameters = ExecutorUtil.getAdditionalParameter(boundSql);
            CacheKey countKey = executor.createCacheKey(countMs, parameter, RowBounds.DEFAULT, boundSql);
            String countSql = dialect.getCountSql(countMs, boundSql, parameter, rowBounds, countKey);
            BoundSql countBoundSql = new BoundSql(countMs.getConfiguration(), countSql, boundSql.getParameterMappings(), parameter);
            additionalParameters.forEach(countBoundSql::setAdditionalParameter);

            // 开启监控
            easyMsDataSourceMonitor.start(countMs.getId(), countBoundSql);

            // 执行count查询无需拦截
            EasyMsInterceptorSkipHolder.skip();

            // 执行查询count
            Object countResultList = executor.query(countMs, parameter, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql);
            return (Long) ((List) countResultList).get(0);
        } catch (SQLException e) {
            // 异常情况监控
            easyMsDataSourceMonitor.sqlException(e);
            throw e;
        } finally {
            // 清除EasyMsInterceptorSkipHolder
            EasyMsInterceptorSkipHolder.clear();

            // 结束监控
            easyMsDataSourceMonitor.end();

            // 提交并关闭executor
            if (executor != null) {
                commitAndClose(executor);
            }
        }
    }

    /**
     * 分页查询
     */
    public <E> List<E> executePageQuery(Dialect dialect, Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
                                        ResultHandler resultHandler, BoundSql boundSql, CacheKey cacheKey) throws SQLException {
        try {
            //判断是否需要进行分页查询
            if (dialect.beforePage(ms, parameter, rowBounds)) {
                parameter = dialect.processParameterObject(ms, parameter, boundSql, cacheKey);
                String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, cacheKey);
                BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);
                Map<String, Object> additionalParameters = ExecutorUtil.getAdditionalParameter(boundSql);
                additionalParameters.forEach(pageBoundSql::setAdditionalParameter);

                // 开启监控
                easyMsDataSourceMonitor.start(ms.getId(), pageBoundSql);

                //执行分页查询
                return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, pageBoundSql);
            } else {

                // 开启监控
                easyMsDataSourceMonitor.start(ms.getId(), boundSql);

                //不执行分页的情况下，也不执行内存分页
                return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
            }
        } catch (SQLException e) {

            // 异常情况监控
            easyMsDataSourceMonitor.sqlException(e);
            throw e;
        } finally {
            // 结束监控
            easyMsDataSourceMonitor.end();
        }
    }

    /**
     * 执行非分页查询或mybatis-plus分页
     */
    public List executeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
                             CacheKey cacheKey, BoundSql boundSql) throws SQLException {
        try {
            // 开启监控
            easyMsDataSourceMonitor.start(ms.getId(), boundSql);

            //执行非分页查询
            return executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
        } catch (SQLException e) {

            // 异常情况监控
            easyMsDataSourceMonitor.sqlException(e);
            throw e;
        } finally {

            // 结束监控
            easyMsDataSourceMonitor.end();
        }
    }

    /**
     * 处理分页count
     */
    public void handlePageCount(PageCountDelayHandleBean bean) {
        final Future<Long> countFuture = bean.getCountFuture();
        Long count;
        try {
            count = countFuture.get(10, TimeUnit.SECONDS);
        } catch (TimeoutException e) {
            log.error("Sql id '{}' execute timeout!", bean.getCountMsId(), e);
            countFuture.cancel(true);
            count = bean.getDefaultCount();
        } catch (ExecutionException e) {
            log.error("Sql id '{}' execute fail!", bean.getCountMsId(), e);
            countFuture.cancel(true);
            count = bean.getDefaultCount();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("Sql id '{}' execute result retrieval is interrupted!", bean.getCountMsId(), e);
            countFuture.cancel(true);
            count = bean.getDefaultCount();
        } catch (CancellationException e) {
            log.error("Sql id '{}' execution has been canceled!", bean.getCountMsId(), e);
            count = bean.getDefaultCount();
        } catch (Exception e) {
            log.error("Sql id '{}' execute result get fail!", bean.getCountMsId(), e);
            countFuture.cancel(true);
            count = bean.getDefaultCount();
        }
        if (count != null) {
            final Object pageObject = bean.getPageObject();
            final PageParameter pageParameter = bean.getPageParameter();
            if (pageObject != null && pageParameter != null) {
                try {
                    int countInt = count.intValue();
                    int pageSize = pageParameter.getPageSize();
                    pageParameter.setTotalCount(countInt);
                    pageParameter.setTotalPage(countInt / pageSize + (countInt % pageSize == 0 ? 0 : 1));
                    BeanUtils.copyProperties(pageParameter, pageObject);
                } catch (Exception e) {
                    log.error("Set sql id '{}' page info fail!", bean.getCountMsId(), e);
                }
            }
            bean.getDialect().afterCount(count, bean.getParameter(), bean.getRowBounds());
        }
    }

    private Executor newExecutor(Configuration configuration) {
        Transaction tx = null;
        try {
            Environment environment = configuration.getEnvironment();
            TransactionFactory transactionFactory = environment.getTransactionFactory();
            tx = transactionFactory.newTransaction(environment.getDataSource(), null, false);
            return configuration.newExecutor(tx, configuration.getDefaultExecutorType());
        } catch (Exception e) {
            if (tx != null) {
                try {
                    tx.close();
                } catch (SQLException ignore) {
                    // Intentionally ignore. Prefer previous error.
                }
            }
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }

    private void commitAndClose(Executor executor) {
        try {
            executor.commit(true);
        } catch (Exception e) {
            log.error("Error committing transaction.  Cause: " + e, e);
        } finally {
            try {
                executor.close(false);
            } finally {
                ErrorContext.instance().reset();
            }
        }
    }

    public void close() {
        // Intentionally blank
    }

    public static EasyMsQueryExecutor getInstance() {
        return EasyMsQueryExecutor.InstanceHolder.INSTANCE;
    }

    private static class InstanceHolder {
        private static final EasyMsQueryExecutor INSTANCE = new EasyMsQueryExecutor();
    }

    private EasyMsQueryExecutor() {
    }
}